Ga naar hoofdinhoud

Tijd en datums

1. Inleiding

Waarom Tijd Complex is in Software

Softwareontwikkelaars ontdekken vaak dat het omgaan met tijd verrassend complex is. Wat op het eerste gezicht eenvoudig lijkt – het weergeven van een datum of het plannen van een gebeurtenis – wordt ingewikkeld wanneer we rekening houden met het mondiale karakter van moderne applicaties. Tijd is werkelijk relatief in softwareontwikkeling, beïnvloed door:

  • Fysieke locatie (tijdzones)
  • Culturele conventies (datumnotaties)
  • Taalvoorkeuren (maandnamen, weekdagnamen)
  • Lokale gebruiken (kalendersystemen)
  • Technische beperkingen (systeemklokken, precisie)

Voorbeeld van Impact in de Praktijk

Beschouw een eenvoudig scenario: Een gebruiker in New York plant een vergadering voor "morgen om 9 uur 's ochtends" met collega's in Londen en Tokyo. Deze enkele gebeurtenis moet correct worden weergegeven voor:

  • New York: 9:00 AM EDT
  • Londen: 2:00 PM BST
  • Tokyo: 10:00 PM JST

Als je applicatie dit niet correct afhandelt, missen mensen vergaderingen, worden deadlines verkeerd begrepen en kunnen bedrijfsoperaties worden verstoord.

2. De Grondbeginselen van Tijd in Computing

Unix Timestamp

De Unix timestamp is de basis van tijdweergave in computing:

// Huidige timestamp
const now = Date.now(); // bijv., 1708107245000

// Een datum maken van timestamp
const date = new Date(now);

// Timestamp krijgen van datum
const timestamp = date.getTime();

Unix tijd (ook bekend als POSIX tijd of Epoch tijd) wordt gedefinieerd als het aantal seconden dat is verstreken sinds 00:00:00 UTC op 1 januari 1970, zonder schrikkelseconden mee te tellen.

De keuze voor 1 januari 1970 als startdatum van de epoch heeft een interessante geschiedenis. Het werd enigszins willekeurig gekozen toen Unix werd ontwikkeld bij Bell Labs in de late jaren '60. Verschillende factoren beïnvloedden deze beslissing:

  1. Het was een recente datum toen Unix werd ontwikkeld, wat handig was voor berekeningen
  2. Het was het begin van een decennium, wat het makkelijker maakte om te onthouden
  3. 1970 valt mooi in het midden van het bereik dat kan worden weergegeven met een 32-bit signed integer (dat datums van 1901 tot 2038 kan weergeven)
  4. De meeste computersystemen uit die tijd konden toch geen datums vóór 1970 verwerken

Het is vermeldenswaard dat hoewel 1 januari 1970 vandaag de dag willekeurig lijkt, het zo universeel is aangenomen dat het nu het standaard referentiepunt is voor computertijdregistratie. Veel andere systemen, zelfs niet-Unix systemen, hebben deze epoch overgenomen om compatibiliteit en consistentie tussen verschillende platforms te behouden.

Een 32-bit signed integer kan getallen weergeven van -2.147.483.648 tot 2.147.483.647. Dit is waarom dit belangrijk is voor Unix tijd:

Aangezien elke seconde wordt geteld vanaf 1 januari 1970, kunnen we de limieten berekenen:

  • Terugwerkend: -2.147.483.648 seconden voor 1970 brengt ons naar 13 december 1901
  • Vooruitwerkend: 2.147.483.647 seconden na 1970 brengt ons naar 19 januari 2038

Dit creëert wat bekend staat als het "Jaar 2038 probleem" of "Y2038": om 03:14:07 UTC op 19 januari 2038 zal de teller overlopen. Als het systeem op dat moment nog steeds een 32-bit integer gebruikt, zou het toevoegen van nog een seconde ervoor zorgen dat het terugspringt naar december 1901.

Dit is waarom veel moderne systemen nu 64-bit integers gebruiken voor timestamps, die datums ver voorbij elke praktische behoefte kunnen weergeven (tot het jaar 292.277.026.596).

Het gebruik van signed integers (die negatief kunnen zijn) in plaats van unsigned was een bewuste keuze - het stelde systemen in staat om datums vóór 1970 weer te geven, wat nuttig was voor verschillende toepassingen, vooral in de financiële en historische domeinen.

UTC en Tijdzone Offsets

UTC (Gecoördineerde Universele Tijd) dient als de mondiale tijdstandaard:

// UTC componenten ophalen
const date = new Date();
console.log(date.getUTCHours()); // Uren in UTC
console.log(date.getHours()); // Uren in lokale tijd
console.log(date.getTimezoneOffset()); // Lokale offset van UTC in minuten

De lokale tijd die wordt gebruikt door date.getHours() wordt bepaald door de systeemtijd van het apparaat waarop de browser draait. De browser leest de tijdzone en tijdinstellingen van het systeem om te bepalen wat "lokale tijd" betekent.

Dit is waarom het belangrijk is om op te merken:

  1. Als een gebruiker hun systeemklok of tijdzone wijzigt, zal dit onmiddellijk de resultaten van getHours() beïnvloeden
  2. Verschillende gebruikers die dezelfde webpagina bekijken kunnen verschillende uren zien als ze in verschillende tijdzones zijn
  3. Als je consistente tijd nodig hebt voor alle gebruikers, moet je UTC methoden zoals getUTCHours() gebruiken

Deze systeemniveau-afhankelijkheid kan soms problemen veroorzaken in applicaties, wat de reden is waarom veel ontwikkelaars de voorkeur geven aan het gebruik van bibliotheken zoals Moment.js of Date-fns die meer expliciete tijdzone-afhandeling bieden, of werken met UTC-tijden en alleen converteren naar lokale tijd op weergaveniveau.

Als je dit wilt testen, kun je de tijdzone-instellingen van je systeem wijzigen en je zult zien dat getHours() verschillende waarden teruggeeft, ook al blijft de onderliggende UTC-tijd hetzelfde.

Tijdzone Uitdagingen

Zomertijd (DST)

// Controleren of een datum in zomertijd valt
const date = new Date('2024-07-01T12:00:00');
const jan = new Date('2024-01-01T12:00:00');
const isDST = date.getTimezoneOffset() < jan.getTimezoneOffset();

Deze code bepaalt of een datum binnen de zomertijd (DST) valt door tijdzone-offsets te vergelijken. Zo werkt het:

getTimezoneOffset() geeft het verschil in minuten tussen UTC en lokale tijd terug. Belangrijk is dat deze offset GROTER is wanneer we niet in DST zijn en KLEINER wanneer we wel in DST zijn. Dit lijkt misschien eerst contra-intuïtief!

Bijvoorbeeld in New York (ET):

  • Tijdens standaardtijd: UTC-5 = offset van 300 minuten
  • Tijdens DST: UTC-4 = offset van 240 minuten

Dus de code vergelijkt:

  1. De offset voor een zomerdatum (1 juli)
  2. De offset voor een winterdatum (1 januari)
  3. Als zomer offset < winter offset, dan is de datum in DST

Wat betreft Zomertijd zelf - het is een praktijk waarbij klokken tijdens warmere maanden (meestal lente/zomer) een uur vooruit worden gezet om daglicht in de avonduren te verlengen. Bijvoorbeeld in Nederland:

  • In de lente gaat de klok een uur vooruit (bijv., 2:00 → 3:00)
  • In de herfst gaat de klok een uur achteruit (bijv., 3:00 → 2:00)

De exacte data voor DST-wijzigingen verschillen per land en regio - sommige plaatsen passen het helemaal niet toe. Dit is waarom het programmatisch controleren van DST belangrijk is voor tijdgevoelige applicaties.

Een kanttekening bij deze code: Hoewel het werkt voor de meeste gevallen, gaat het ervan uit dat DST-regels het hele jaar door hetzelfde blijven. Als een land zijn DST-regels halverwege het jaar wijzigt, geeft deze methode mogelijk geen nauwkeurige resultaten.

Internationale Datumgrens

// Zelfde moment, verschillende datums
const date = new Date('2024-02-16T23:00:00Z');
console.log(date.toLocaleString('en-US', { timeZone: 'America/Los_Angeles' }));
console.log(date.toLocaleString('en-US', { timeZone: 'Asia/Tokyo' }));

3. De Y2K Bug - Historische Context

Het Oorspronkelijke Probleem

Wat was het? Veel computersystemen in de 20e eeuw sloegen jaren op als tweecijferige waarden (bijv. "99" voor 1999). Toen het jaar 2000 aanbrak, interpreteerden systemen die geen rekening hielden met de eeuwwisseling "00" als 1900 in plaats van 2000. Dit leidde tot onjuiste berekeningen in datumafhankelijke software.

Waarom gebeurde het? Geheugen en opslag waren duur in de begindagen van computing, dus programmeurs gebruikten tweecijferige jaren om ruimte te besparen. Ze hadden niet voorzien dat deze systemen nog steeds in gebruik zouden zijn in 2000.

Effecten:

  • Bank-, vliegtuigreserverings- en overheidssystemen liepen risico op uitval.
  • Sommige software berekende leeftijden, deadlines en rentetarieven onjuist.
  • Overheden en bedrijven besteedden miljarden aan het upgraden van hun systemen om een crisis te voorkomen.

Lessen voor Frontend Ontwikkelaars:

  • Sla datums altijd correct op (bijv. gebruik viercijferige jaren).
  • Wees voorzichtig met legacy code bij het werken met oude systemen.
  • Testen en toekomstbestendigheid zijn cruciaal bij het omgaan met tijdgerelateerde gegevens.
// Pre-Y2K stijl datum opslag
const year = 99; // Representeert 1999
const fullYear = year < 50 ? 2000 + year : 1900 + year;

// Moderne aanpak
const date = new Date();
const modernYear = date.getFullYear(); // Altijd vier cijfers

Het 2038 Probleem

Wat is het? Veel systemen slaan tijd op als het aantal seconden sinds 1 januari 1970 (Unix Epoch) in een 32-bit signed integer.

De maximale waarde voor een 32-bit signed integer is 2.147.483.647, wat overeenkomt met 19 januari 2038 om 03:14:07 UTC. Na dit punt loopt de waarde over en reset naar een negatief getal, wat kan leiden tot crashes of onjuiste tijdberekeningen.

Waarom is dit een probleem? Systemen die 32-bit tijdsweergave gebruiken, zoals sommige embedded systems, financiële software en oude Unix-gebaseerde besturingssystemen, zullen falen. Anders dan Y2K treft dit vooral lagere-niveau systemen, maar alles wat afhankelijk is van deze timestamps (inclusief sommige webservices) kan worden beïnvloed.

Potentiële Effecten:

  • Tijdberekeningen kunnen kapot gaan in oude software.
  • Sommige apparaten kunnen crashen of onvoorspelbaar gedrag vertonen.
  • Bank-, plannings- en authenticatiesystemen kunnen falen als ze getroffen timestamps gebruiken.

Lessen voor Frontend Ontwikkelaars:

  • Gebruik altijd moderne datum bibliotheken zoals JavaScript's Date object, Intl.DateTimeFormat, of bibliotheken zoals date-fns die tijd correct afhandelen.
  • Wees bewust van backend tijd opslagformaten (zorg ervoor dat ze 64-bit timestamps gebruiken).
  • Plan voor langetermijnondersteuning van applicaties voorbij huidige technologische aannames.
// 32-bit Unix timestamp limiet
const maxTimestamp = Math.pow(2, 31) - 1;
const epochEnd = new Date(maxTimestamp * 1000);
console.log(epochEnd); // Tue Jan 19 2038 03:14:07

4. Frontend Ontwikkeling Best Practices

Gegevensuitwisseling met Backend

Gebruik Altijd ISO 8601

// GOED: ISO 8601 formaat
const goodFormat = '2024-02-16T12:30:00Z';
const betterFormat = '2024-02-16T12:30:00.000Z'; // Met milliseconden

// SLECHT: Dubbelzinnige formaten
const badFormat1 = '02/16/2024'; // Is dit MM/DD of DD/MM?
const badFormat2 = '2024-02-16'; // Geen tijdcomponent

Tijdzone Afhandeling

// GOED: Expliciete tijdzone afhandeling
const userInput = '2024-02-16 12:30';
const userTimezone = 'Europe/Amsterdam';

// Converteren naar UTC voor opslag/transmissie
const utcDate = new Date(userInput + ' ' + userTimezone);
const isoString = utcDate.toISOString();

// Terugconverteren naar gebruiker's tijdzone voor weergave
const options = {
timeZone: userTimezone,
dateStyle: 'full',
timeStyle: 'long'
};
const localDisplay = utcDate.toLocaleString('nl-NL', options);

Gebruikersinterface Overwegingen

Locale-Bewuste Formattering

const date = new Date();

De i18n bibliotheek ingebouwd in JS is [Intl](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl)

// Datum formattering voor verschillende locales
const formatters = {
nl: new Intl.DateTimeFormat('nl-NL', {
dateStyle: 'full',
timeStyle: 'long'
}),
duits: new Intl.DateTimeFormat('de-DE', {
dateStyle: 'full',
timeStyle: 'long'
}),
japans: new Intl.DateTimeFormat('ja-JP', {
dateStyle: 'full',
timeStyle: 'long'
})
};

console.log(formatters.nl.format(date));
console.log(formatters.duits.format(date));
console.log(formatters.japans.format(date));

Relatieve Tijdweergave

const rtf = new Intl.RelativeTimeFormat('nl', { numeric: 'auto' });

function getRelativeTimeString(date) {
const now = new Date();
const diff = date.getTime() - now.getTime();

const diffInSeconds = diff / 1000;
const diffInMinutes = diffInSeconds / 60;
const diffInHours = diffInMinutes / 60;
const diffInDays = diffInHours / 24;

if (Math.abs(diffInSeconds) < 60) {
return rtf.format(Math.round(diffInSeconds), 'second');
} else if (Math.abs(diffInMinutes) < 60) {
return rtf.format(Math.round(diffInMinutes), 'minute');
} else if (Math.abs(diffInHours) < 24) {
return rtf.format(Math.round(diffInHours), 'hour');
} else {
return rtf.format(Math.round(diffInDays), 'day');
}
}

5. Modern Bibliotheekgebruik

Date-fns Voorbeeld

date-fns

import { format, formatDistance, parseISO } from 'date-fns'
import { nl } from 'date-fns/locale'

// Formattering
format(new Date(), 'yyyy-MM-dd HH:mm:ss')

// Relatieve tijd
formatDistance(
new Date('2024-02-16'),
new Date('2024-02-15'),
{ addSuffix: true, locale: nl }
) // "1 dag geleden"

Luxon Voorbeeld

Luxon

import { DateTime } from 'luxon'

// Werken met tijdzones
const dt = DateTime.fromISO('2024-02-16T12:00:00', {
zone: 'Europe/Amsterdam'
})
console.log(dt.setZone('Asia/Tokyo').toISO())

// Formattering
console.log(dt.toFormat('yyyy-MM-dd HH:mm:ss ZZZZ'))

6. De Toekomst: Temporal Voorstel

Het Temporal Voorstel is een nieuwe JavaScript API ontworpen om veel van de problemen met het bestaande Date object op te lossen. Hoewel het nog in ontwikkeling is (Stage 3 vanaf begin 2024), vertegenwoordigt het de toekomst van datum/tijd afhandeling in JavaScript.

Belangrijkste Kenmerken

// Huidige Date API problemen
const now = new Date();
now.getMonth(); // 👎 Nul-gebaseerde maanden (0-11)
now.setMonth(13); // 👎 Stilzwijgende overflow
new Date("2024-02-30").toString(); // 👎 Stilzwijgende datumcorrectie

// Temporal API oplossingen
const now = Temporal.Now.instant();
const nlZone = Temporal.TimeZone.from('Europe/Amsterdam');
const nlNow = now.toZonedDateTimeISO(nlZone);

// Duidelijke, onveranderlijke datummanipulatie
const date = Temporal.PlainDate.from('2024-02-16');
const nextMonth = date.add({ months: 1 }); // Retourneert nieuwe instantie
const nextYear = date.add({ years: 1 }); // Originele datum ongewijzigd

Belangrijke Verbeteringen

  1. Onveranderlijk door Ontwerp
  2. Duidelijke Types voor Verschillende Gebruikssituaties
  3. Expliciete Tijdzone Afhandeling
  4. Verbeterde Kalenderondersteuning

7. Extra onderwerpen

Schrikkelseconden & Precisie Problemen

  • Uitleg waarom UTC soms 61 seconden in een minuut heeft.
  • Bespreking hoe sommige systemen schrikkelseconden negeren (zoals Unix tijd) terwijl andere zich aanpassen.
  • Vermelding dat JavaScript's Date object geen rekening houdt met schrikkelseconden.

JavaScript Date Eigenaardigheden

  • Stilzwijgende Overflow & Auto-Correctie:
    console.log(new Date(2024, 1, 30)); // Auto-corrigeert naar 1 maart
  • Omgang met Ongeldige Datums:
    console.log(new Date('Invalid Date').toString()); // "Invalid Date"

Tijdafhandeling in Web Performance

  • Wanneer performance.now() vs. Date.now() te gebruiken
  • Timestamps gebruiken voor animaties, uitvoeringstijd meten.

Randgevallen in Datummanipulatie

  • Maanden of jaren toevoegen/aftrekken:
    let date = new Date(2024, 0, 31);
    date.setMonth(date.getMonth() + 1);
    console.log(date); // Niet 31 feb, auto-corrigeert naar 2 maart!

Datumafhandeling in IndexedDB & Local Storage

  • Het opslaan van datums in localStorage kan onverwacht gedrag veroorzaken als het formaat niet gestandaardiseerd is.

Interoperabiliteit met Andere Systemen

  • Hoe verschillende backends timestamps afhandelen (bijv., MySQL DATETIME vs. JavaScript Date).

8. Bronnen en Tools

Officiële Documentatie

Bibliotheken

Ontwikkelingstools

Best Practices Samenvatting

  1. Gegevensopslag en Transmissie

    • Sla datums altijd op in UTC
    • Gebruik ISO 8601 formaat voor API communicatie
    • Voeg tijdzone-informatie toe wanneer relevant
    • Sla nooit lokale tijd op zonder tijdzonecontext
  2. Gebruikersinterface

    • Toon datums in de lokale tijdzone van de gebruiker
    • Gebruik locale-bewuste formattering
    • Geef tijdzonecontext weer bij het tonen van tijden
    • Overweeg relatieve tijd voor recente datums
  3. Invoerverwerking

    • Valideer datuminvoer server-side
    • Gebruik HTML5 datuminvoervelden waar mogelijk
    • Geef duidelijke formaatrichtlijnen
    • Houd rekening met tijdzone bij het verwerken van gebruikersinvoer
  4. Testen

    • Test met verschillende tijdzones
    • Test rond DST-overgangen
    • Test met verschillende locales
    • Test datum parsing randgevallen

Veelvoorkomende Valkuilen

  1. Aannemen dat alle dagen 24 uur hebben (DST-overgangen)
  2. Aannemen dat maanden 30/31 dagen hebben
  3. new Date() gebruiken zonder tijdzonecontext
  4. Datums parsen zonder expliciete formaten
  5. Datums opslaan zonder tijdzone-informatie
  6. Lokale tijd gebruiken voor bedrijfslogica
  7. Datums vergelijken zonder tijdzones te normaliseren